#include <assert.h>

#include "graph.h"

#define swap(a,b) { __typeof(a) tmp; tmp=a; a=b; b=tmp; }

void horz_gradient_rect(widget, rect, top_color, bottom_color)
    widget_t *widget;
    RECT *rect;
    rgb_t top_color;
    rgb_t bottom_color;
{

    uint32_t y_pitch = widget->width;
    double height = (double)(rect->bottom - rect->top);
    // calculate the delta change for the color
    double dr = (bottom_color.r - top_color.r) / (height);
    double dg = (bottom_color.g - top_color.g) / (height);
    double db = (bottom_color.b - top_color.b) / (height);
    // set the start color
    double r = (double)top_color.r;
    double g = (double)top_color.g;
    double b = (double)top_color.b;

    uint32_t x1 = rect->left;
    uint32_t x2 = rect->right;
    uint32_t y;

    assert(widget->backbuf != NULL);

    // make sure the right edge is greater then the left
    if(x1 >= x2) {
        return;
    }

    // do not draw the rectangle if both left and right is outside the
    // widget area
    if((x1 < 0 && x2 < 0) || (x1 > widget->width && x2 > widget->width)) {
        return;
    }

    // clip the left and right to widget client edges if applicable
    if(x1 < 0) {
        x1 = 0;
    }
    if(x2 > widget->width) {
        x2 = widget->width - 1;
    }

    // scan the y line
    for(y = rect->top; y < rect->bottom; y++)
    {
        // calculate the color gradient
        rgb_t rgb;
        rgb.r = (uint8_t)(r + 0.5);
        rgb.g = (uint8_t)(g + 0.5);
        rgb.b = (uint8_t)(b + 0.5);
        uint32_t color = *((uint32_t *)&rgb);

        // only draw the scan line if its within the width client area
        if(y < widget->height && y >= 0) {
            // calculate start and end offset
            uint32_t *obegin = widget->backbuf+(y * y_pitch + x1);
            uint32_t *oend = obegin + (x2 - x1);
            // draw a vertical line with the color
            while(obegin < oend) {
                *(obegin++) = color;
            }
        }
        // update the color
        r += dr;
        g += dg;
        b += db;
    }
}

void vert_gradient_rect(widget, rect, left_color, right_color)
    widget_t *widget;
    RECT *rect;
    rgb_t left_color;
    rgb_t right_color;
{

    uint32_t y_pitch = widget->width;
    double width = (double)(rect->right - rect->left);
    // calculate the delta change for the color
    double dr = (right_color.r - left_color.r) / width;
    double dg = (right_color.g - left_color.g) / width;
    double db = (right_color.b - left_color.b) / width;
    // set the start color
    double r = (double)left_color.r;
    double g = (double)left_color.g;
    double b = (double)left_color.b;

    uint32_t x;
    uint32_t y1 = rect->top;
    uint32_t y2 = rect->bottom;

    assert(widget->backbuf != NULL);

    // make sure the bottom edge is greate then the top edge
    if(y1 >= y2) {
        return;
    }

    // do not draw the rectangle if both top and bottom is outside the
    // widget area
    if((y1 < 0 && y2 < 0) || (y1 > widget->height && y2 > widget->height)) {
        return;
    }

    // clip the left and right to widget client edges if applicable
    if(y1 < 0) {
         y1 = 0;
    }
    if(y2 > widget->height) {
        y2 = widget->height - 1;
    }

    // scan the x line
    for(x = rect->left; x < rect->right; x++)
    {
        // calculate the color gradient
        rgb_t rgb;
        rgb.r = (uint8_t)(r + 0.5);
        rgb.g = (uint8_t)(g + 0.5);
        rgb.b = (uint8_t)(b + 0.5);
        uint32_t color = *((uint32_t *)&rgb);

        // only draw the scan line if its within the width client area
        if(x < widget->width && x >= 0) {
            // calculate start and end offset
            uint32_t *obegin = widget->backbuf+(y1 * y_pitch + x);
            uint32_t *oend = widget->backbuf+(y2 * y_pitch + x);
            // draw a vertical line with the color
            while(obegin < oend) {
                *obegin = color;
                obegin += y_pitch;
            }
        }
        // update the color
        r += dr;
        g += dg;
        b += db;
    }
}

void solid_rect(widget, rect, color)
    widget_t *widget;
    RECT *rect;
    rgb_t color;
{
    rgb_t *obegin;
    rgb_t *oend;
    rgb_t *buf = (rgb_t *)widget->backbuf;
    int32_t y_pitch = widget->width;
    int32_t x1 = rect->left;
    int32_t x2 = rect->right;
    int32_t y;

    assert(widget->backbuf != NULL);

    // make sure the right edge is greater then the left
    if(x1 >= x2) {
        return;
    }

    // do not draw the rectangle if both left and right is outside the
    // widget area
    if((x1 < 0 && x2 < 0) || (x1 > widget->width && x2 > widget->width)) {
        return;
    }

    // clip the left and right to widget client edges if applicable
    if(x1 < 0) {
         x1 = 0;
    }
    if(x2 > widget->width) {
        x2 = widget->width - 1;
    }

    for(y=rect->top; y<rect->bottom; y++)
    {
        if(y>=0 && y<widget->height)
        {
            obegin = buf + (y * y_pitch + x1);
            oend = obegin + (x2 - x1);
            while(obegin < oend)
            {
                *obegin = color;
                obegin++;
            }
        }
    }
}

void line(widget, x1, y1, x2, y2, color)
    widget_t *widget;
    int32_t x1, y1;
    int32_t x2, y2;
    rgb_t color;
{
    uint16_t error_adj, error_acc, error_acc_tmp;
    int32_t dx, dy, xstep;
    uint32_t *buf = widget->backbuf;
    rgb_t backcol; // background color
    rgb_t rescol; // result color
    uint16_t w; // weight
    int32_t width = widget->width-1;
    int32_t height = widget->height-1;
    rgb_t *offset;
    uint8_t code1;
    uint8_t code2;
    uint8_t done=0;
    int32_t x;
    int32_t y;
    uint32_t y_pitch = widget->width;

    // make sure that the widget have a back buffer
    assert(buf != NULL);

    // clip the line using Cohen-Sutherland algorithm
    // do not clip line with zero length
    if(x1 == x2 && y1 == y2) {
        return;
    }

    do {
        code1 = code2 = 0;
        // find clip code for point 1
        if      (y1 < 0)      code1 |= 1; // bottom;
        else if (y1 > height) code1 |= 2; // top
        if      (x1 < 0)      code1 |= 4; // left
        else if (x1 > width)  code1 |= 8; // right

        // find clip code for point 2
        if      (y2 < 0)      code2 |= 1; // bottom
        else if (y2 > height) code2 |= 2; // top
        if      (x2 < 0)      code2 |= 4; // left
        else if (x2 > width)  code2 |= 8; // right

        if((code1 & code2) != 0) {
            // line not visable
            return;
        }

        if((code1 | code2) == 0) {
            // line within the client area
            done = 1;
        }
        else {
            // line need clipping
            if(code1 != 0) {
                if(code1 & 1) {
                    // clip against the bottom
                    x = x2 - (y2 * (x1 - x2)) / (y1 - y2);
                    y = 0;
                }
                else if(code1 & 2) {
                    // clip against the top
                    x = x2 + ((x1 - x2) * (height - y2)) / (y1 - y2);
                    y = height;
                }
                if(code1 & 4) {
                    // clip against the left
                    y = y2 - (x2 * (y1 - y2)) / (x1 - x2);
                    x = 0;
                }
                else if(code1 & 8) {
                    // clip against the right
                    y = y2 + ((y1 - y2) * (width - x2)) / (x1 - x2);
                    x = width;
                }
                x1 = x;
                y1 = y;
            }
            else {
                if(code2 & 1) {
                    // clip against the bottom
                    x = x1 - (y1 * (x2 - x1)) / (y2 - y1);
                    y = 0;
                }
                else if(code2 & 2) {
                    // clip against the top
                    x = x1 + ((x2 - x1) * (height - y1)) / (y2 - y1);
                    y = height;
                }
                if(code2 & 4) {
                    // clip against the left
                    y = y1 - (x1 * (y2 - y1)) / (x2 - x1);
                    x = 0;
                }
                else if(code2 & 8) {
                    // clip against the right
                    y = y1 + ((y2 - y1) * (width - x1)) / (x2 - x1);
                    x = width;
                }
                x2 = x;
                y2 = y;
            }
        }
    } while(done != 1);

    // Draw an antialiasing line using Xiaolin Wu's line algotithm
    // do not draw line with zero length
    if(x1 == x2 && y1 == y2) {
        return;
    }

    // draw initial and final pixel
    buf[y1 * y_pitch + x1] = *((uint32_t *)&color);
    buf[y2 * y_pitch + x2] = *((uint32_t *)&color);

    // make sure the line runs from top to bottom
    if(y1 > y2) {
        swap(x1, x2);
        swap(y1, y2);
    }

    if((dx = x2 - x1) >= 0) {
        xstep = 1;
    }
    else {
        xstep = -1;
        dx = -dx; // make delta x positive
    }

    // special cases horizontal, veritcal and diagonal lines
    if((dy = y2 - y1) == 0) {
        // horizontal line
        offset = (rgb_t *)(buf + y1 * y_pitch + x1);
        while(dx--) {
            *offset = color;
            offset += xstep;
        }
        return;
    }
    if(dx == 0) {
        // vertical line
        offset = (rgb_t *)(buf + y1 * y_pitch + x1);
        do {
            *offset = color;
            offset += y_pitch;
        } while(--dy != 0);
        return;
    }

    if(dx == dy) {
        // diagonal line
        offset = (rgb_t *)(buf + y1 * y_pitch + x1);
        xstep = y_pitch + xstep;
        do {
            *offset = color;
            offset += xstep;
        } while(--dy != 0);
        return;
    }

    error_acc = 0;

    if(dy > dx) {
        // "verticalis" line
        error_adj = ((uint32_t)dx << 16) / (uint32_t)dy;
        
        while(--dy) {
            // calculate the new y and x for the pixel
            error_acc_tmp = error_acc;
            error_acc += error_adj;
            if(error_acc <= error_acc_tmp) {
                x1 += xstep;
            }
            y1++;
            
            // draw the "right" pixel
            w = error_acc >> 8; // calculate the weight
            offset = (rgb_t *)(buf + y1 * y_pitch + x1 + xstep);
            // alpha blend line color with its weight and the
            // background color
            backcol = *offset;
            rescol.r = backcol.r + ((w * (color.r - backcol.r)) >> 8);
            rescol.g = backcol.g + ((w * (color.g - backcol.g)) >> 8);
            rescol.b = backcol.b + ((w * (color.b - backcol.b)) >> 8);
            // draw the pixel to the background buffer
            *offset = rescol;

            // draw the "left" pixel
            w = w ^ 255; // invert the weigth
            offset -= xstep;
            // alpha blend the color with its inverted weight and the
            // background color
            backcol = *offset;
            rescol.r = backcol.r + ((w * (color.r - backcol.r)) >> 8);
            rescol.g = backcol.g + ((w * (color.g - backcol.g)) >> 8);
            rescol.b = backcol.b + ((w * (color.b - backcol.b)) >> 8);
            // draw the pixel to the background buffer
            *offset = rescol;
        }
        return;
    }

    // "horizontic" line
    error_adj = ((uint32_t)dy << 16) / (uint32_t)dx;
    
    while(--dx) {
        // calculate the new x and y for the pixel
        error_acc_tmp = error_acc;
        error_acc += error_adj;
        if(error_acc <= error_acc_tmp) {
            y1++;
        }
        x1 += xstep;

        // draw the "upper" pixel
        w = error_acc >> 8; // calculate the weight
        offset = (rgb_t *)(buf + (y1+1) * y_pitch + x1);
        // alpha blend "line" color with its weight and the
        // background color
        backcol = *offset;
        rescol.r = backcol.r + ((w * (color.r - backcol.r)) >> 8);
        rescol.g = backcol.g + ((w * (color.g - backcol.g)) >> 8);
        rescol.b = backcol.b + ((w * (color.b - backcol.b)) >> 8);
        // draw the pixel to the background buffer
        *offset = rescol;

        // draw the "lower" pixel
        w = w ^ 255; // invert the weigth
        offset -= y_pitch;
        // alpha blend the line color with its inverted weight and the
        // background color
        backcol = *offset;
        rescol.r = backcol.r + ((w * (color.r - backcol.r)) >> 8);
        rescol.g = backcol.g + ((w * (color.g - backcol.g)) >> 8);
        rescol.b = backcol.b + ((w * (color.b - backcol.b)) >> 8);
        // draw the pixel to the background buffer'
        *offset = rescol;
    }
}

#undef swap

